#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "stm32f4xx.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "main.h"
#include "workqueue.h"
#include "esp_at.h"
#include "image.h"
#include "dht11.h"

#define LOG_TAG "app"
#define LOG_LVL ELOG_LVL_INFO
#include "elog.h"

typedef void (*app_job_t) (void);

#define SECOND(x)           (x * 1000)
#define HOURS(x)            (x * 60ul * 1000ul * 60ul)
#define MINUT(x)            (x * 60ul * 1000ul)

#define WIFI_UPDATE          SECOND(10)
#define TIME_SYNC             HOURS(5)
#define DHT11_UPDATE         SECOND(15)
#define TIME_UPDATE          SECOND(1)
#define OUTDOOR_UPDATE       MINUT(1)

static TimerHandle_t time_update_timer;
static TimerHandle_t time_sync_timer;
static TimerHandle_t wifi_update_timer;
static TimerHandle_t innner_update_timer;
static TimerHandle_t outdoor_update_timer;

static struct dht11 sensor;
static RTC_DateTypeDef rtc_date;
static RTC_TimeTypeDef rtc_time;

void sensor_init(void)
{
    taskENTER_CRITICAL();
    dht11_init(&sensor, GPIOA, GPIO_Pin_6);
    taskEXIT_CRITICAL();
}

static void app_clock_set(rtc_date_time_t *info)
{
    rtc_time.RTC_H12 = RTC_H12_AM;
    rtc_time.RTC_Hours = info->hour;
    rtc_time.RTC_Minutes = info->minute;
    rtc_time.RTC_Seconds = info->seconds;
    RTC_SetTime(RTC_Format_BIN, &rtc_time);

    rtc_date.RTC_Year = (info->year % 2000);
    rtc_date.RTC_Month = info->month;
    rtc_date.RTC_Date = info->day;
    rtc_date.RTC_WeekDay = info->weekday;
    RTC_SetDate(RTC_Format_BIN, &rtc_date);
}

static RTC_TimeTypeDef app_clock_update_once(void)
{
    RTC_TimeTypeDef rtc_time1, rtc_time2;
    do
    {
        RTC_GetTime(RTC_Format_BIN, &rtc_time1);
        RTC_GetTime(RTC_Format_BIN, &rtc_time2);
    } while (rtc_time1.RTC_Seconds != rtc_time2.RTC_Seconds);

    return rtc_time1;
}

static void app_clock_update(rtc_date_time_t *info)
{
    rtc_time = app_clock_update_once();
    RTC_GetDate(RTC_Format_BIN, &rtc_date);

    info->year = rtc_date.RTC_Year;
    info->month = rtc_date.RTC_Month;
    info->day = rtc_date.RTC_Date;
    info->hour = rtc_time.RTC_Hours;
    info->minute = rtc_time.RTC_Minutes;
    info->seconds = rtc_time.RTC_Seconds;
    info->weekday = rtc_date.RTC_WeekDay;
}

static void time_update(void)
{
    static rtc_date_time_t last_date = {0};

    rtc_date_time_t date;
    app_clock_update(&date);

    if (date.year < 20)
        return;

    if (memcmp(&date, &last_date, sizeof(rtc_date_time_t)) == 0)
        return;

    memcpy(&last_date, &date, sizeof(rtc_date_time_t));

    taskENTER_CRITICAL(); // 临界区DMA发送完成触发中断导致卡死
    // log_i("time update: %u-%u-%u, %u-%u-%u", date.year, date.month, date.day,
           // date.hour, date.minute, date.seconds);
    time_redraw_display(&date);
    taskEXIT_CRITICAL();
}

static void time_sync(void)
{
    uint32_t restart_sync_delay = TIME_SYNC;
    rtc_date_time_t rtc_date = {0};

    esp_date_time_t esp_date = {0};
    if (!esp_at_get_date_time(&esp_date)) 
    {
        log_e("get time faild");
        restart_sync_delay = SECOND(1);
        goto err;
    }

    log_d("get time ok: %04u-%02u-%02u %02u:%02u:%02u (%d)\n",
    esp_date.year, esp_date.month, esp_date.day,
    esp_date.hour, esp_date.minute, esp_date.second, esp_date.weekday);
    
    if (esp_date.year < 20)
    {
        log_e("invalid date formate");
        restart_sync_delay = SECOND(1);
        goto err;
    }

    // log_i("sync time: %04u-%02u-%02u %02u:%02u:%02u (%d)\n",
    // esp_date.year, esp_date.month, esp_date.day,
    // esp_date.hour, esp_date.minute, esp_date.second, esp_date.weekday);
    
    rtc_date.year = esp_date.year;
    rtc_date.month = esp_date.month;
    rtc_date.day = esp_date.day;
    rtc_date.hour = esp_date.hour;
    rtc_date.minute = esp_date.minute;
    rtc_date.seconds = esp_date.second;
    rtc_date.weekday = esp_date.weekday;
    app_clock_set(&rtc_date);

err:
    xTimerChangePeriod(time_sync_timer, pdMS_TO_TICKS(restart_sync_delay), 0);
}

static void wifi_update(void)
{
    static esp_wifi_info_t last_wifi = {0};

    esp_wifi_info_t wifi = {0};
    if (!esp_at_get_wifi_info(&wifi))
    {
        wifi_redraw_display("Unknown Device");
        log_e("wifi info get faild");
        return;
    }
    
    if (memcmp(&last_wifi, &wifi, sizeof(esp_wifi_info_t)) != 0)
    {
        wifi_redraw_display(wifi.ssid);
        return;
    }

    if (wifi.connected)
    {
        log_d("wifi connected: %d", wifi.ssid);
        log_d("SSID: %s, BSSID: %s, Channnel: %d, RSSI: %d", wifi.ssid, wifi.bssid, wifi.channel, wifi.rssi);
        wifi_redraw_display(wifi.ssid);
    }
    else
    {
        log_e("disconnected from: %s", last_wifi.ssid);
        wifi_redraw_display("Unknown Device");
    }

    memcpy(&last_wifi, &wifi, sizeof(esp_wifi_info_t));
}

static void innner_update(void)
{   
    static struct dht11 last_sensor = {0};

    taskENTER_CRITICAL();
    bool ret = dht_receive_data(&sensor);
    taskEXIT_CRITICAL();

    if (!ret) 
    {
        log_e("sensor receive data faild");
        return;
    }

    log_i("sensor data, %.d, %.d", sensor.temperature, sensor.humidity);

    if (memcmp(&last_sensor, &sensor, sizeof(struct dht11)) == 0)
    {
        log_d("sensor data not changed");
        return;
    }

    memcpy(&last_sensor, &sensor, sizeof(struct dht11));
    
    sensor_redraw_display((int)sensor.temperature, (int)sensor.humidity);
}

static void outdoor_update(void)
{
    // Fuzhou
    const char* weather_url = "https://api.seniverse.com/v3/weather/now.json?key=SLsBkukR4U7bK3jiw&location=WSSU6EXX52RE&language=en&unit=c";

    // Fuqing
    // const char* weather_url = "https://api.seniverse.com/v3/weather/now.json?key=SLsBkukR4U7bK3jiw&location=WSSFKD1VGEEB&language=en&unit=c";
    static weather_info_t last_weather = {0};
    weather_info_t weather = {0};

    const char* weather_http_response = esp_at_http_get(weather_url);
    if(weather_http_response == NULL)
    {
        log_e("weather http get faild");
        return;
    }

    if(!parse_seniverse_response(weather_http_response, &weather))
    {
        log_e("parse weather info faild");
        return;
    }

    if (memcmp(&last_weather, &weather, sizeof(weather_info_t)) == 0)
    {
        return;
    }

    log_d("%s, %s, %.1f", weather.city, weather.weather, weather.temperature);

    memcpy(&last_weather, &weather, sizeof(weather_info_t));

    http_redraw_display(&weather);
}

static void app_work(void* param)
{
    app_job_t job = (app_job_t)param;
    job();
}

static void work_timer_callback(TimerHandle_t timer)
{
    app_job_t job = (app_job_t)pvTimerGetTimerID(timer);
    workqueue_run(app_work, job);
}

static void app_timer_callback(TimerHandle_t timer)
{
    app_job_t job = (app_job_t)pvTimerGetTimerID(timer);
    job();
}

void app_init(void)
{
    time_update_timer = xTimerCreate("time update", pdMS_TO_TICKS(TIME_UPDATE), pdTRUE, time_update, app_timer_callback);
    time_sync_timer = xTimerCreate("time sync", pdMS_TO_TICKS(200), pdFALSE, time_sync, work_timer_callback);
    wifi_update_timer = xTimerCreate("wifi update", pdMS_TO_TICKS(WIFI_UPDATE), pdTRUE, wifi_update, work_timer_callback);
    innner_update_timer = xTimerCreate("inner update", pdMS_TO_TICKS(DHT11_UPDATE), pdTRUE, innner_update, work_timer_callback);
    outdoor_update_timer = xTimerCreate("outdoor update", pdMS_TO_TICKS(OUTDOOR_UPDATE), pdTRUE, outdoor_update, work_timer_callback);

    workqueue_run(app_work, time_sync);
    workqueue_run(app_work, wifi_update);
    workqueue_run(app_work, innner_update);
    workqueue_run(app_work, outdoor_update);

    xTimerStart(time_update_timer, 0);
    xTimerStart(time_sync_timer, 0);
    xTimerStart(wifi_update_timer, 0);
    xTimerStart(innner_update_timer, 0);
    xTimerStart(outdoor_update_timer, 0);
}
